﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Inscriptionem.Authorization;
using Inscriptionem.Controllers.Base;
using Inscriptionem.Extensions;
using Inscriptionem.Localization;
using Inscriptionem.Models.Blog;
using Inscriptionem.Models.Blog.Base;
using Inscriptionem.Models.Blog.Requests;
using Inscriptionem.Models.Blog.Views;
using Inscriptionem.Models.Menu.Requests;
using Inscriptionem.MVC;
using Inscriptionem.ORM.Extensions;

namespace Inscriptionem.Controllers
{
    [BlogActionFilter]
    public class BlogController : BaseController
    {
        #region Classes
        public class ArticleCollectionWrapper
        {
            public ArticleCollectionWrapper()
            {
                Articles = new List<Article>();
                Pagination = new BaseViewModel.PaginationModel();
            }

            public List<Article> Articles { get; protected set; }
            public BaseViewModel.PaginationModel Pagination { get; protected set; }
        }
        #endregion
        #region Static Properties
        protected static int MaxPageSize
        {
            get
            {
                return Configuration.Configuration.Instance.Blog.PageSize;
            }
        }

        public static bool IsCommentsAllowed
        {
            get
            {
                return Member.Current.ID != 0 && Configuration.Configuration.Instance.Comments.Allowed && !Member.Current.IsReadOnly;
            }
        }
        #endregion

        protected override void Execute(RequestContext requestContext)
        {
            // Check section
            var language = new ORM.DB().Languages.First(l => l.Name.ToLower() == Language.Current.Value.ToLower());

            if (requestContext.RouteData.Values["section"] == null ||
                (requestContext.RouteData.Values["section"] != null && string.IsNullOrWhiteSpace(requestContext.RouteData.Values["section"].ToString())))
            {
                var section = language.Sections.FirstOrDefault(s => s.IsDefault);
                if (section == null)
                {
                    throw new HttpException(404, "Not found!");
                }

                requestContext.RouteData.Values["section"] = section.Name;
            }

            // Override URLs like /1
            if (requestContext.RouteData.Values["action"] != null)
            {
                int id = 0;
                if (int.TryParse(requestContext.RouteData.Values["action"].ToString(), out id))
                {
                    // Rewrite route data
                    requestContext.RouteData.Values["action"] = "index";
                    requestContext.RouteData.Values["id"] = id;
                }
            }

            base.Execute(requestContext);
        }

        public ActionResult Index(BlogRequestModel model)
        {
            if (model.ID < 0)
            {
                return Redirect(string.Format("/{0}/{1}", model.Language, model.Section));
            }

            // Trick for Index, that won't have navigation url parameter
            model.Navigation = model.ID;

            var result = new BlogViewModel();
            model.CopyTo(result);

            var articles = PopulateSection(result);
            result.Articles.AddRange(articles.Articles);
            result.Pagination = articles.Pagination;

            if (model.ID != 0 && result.Articles.Count == 0)
            {
                throw new HttpException(404, "No entries found!");
            }

            return View("Index", result);
        }

        public ActionResult Category(BlogRequestModel model)
        {
            if (model.ID < 0 || model.Navigation < 0)
            {
                return new HttpNotFoundResult();
            }

            var result = new CategoryViewModel();
            model.CopyTo(result);

            var articles = PopulateCategory(result);
            result.Articles.AddRange(articles.Articles);
            result.Pagination = articles.Pagination;

            if (result.Articles.Count == 0)
            {
                throw new HttpException(404, "No entries found!");
            }

            return View("Category", result);
        }

        public ActionResult Archive(BlogRequestModel model)
        {
            if (model.ID < 0 || model.Navigation < 0)
            {
                return new HttpNotFoundResult();
            }

            var result = new ArchiveViewModel();
            model.CopyTo(result);

            var articles = PopulateArchive(result);
            result.Articles.AddRange(articles.Articles);
            result.Pagination = articles.Pagination;

            if (result.Articles.Count == 0)
            {
                throw new HttpException(404, "No entries found!");
            }

            return View("Archive", result);
        }

        public ActionResult Post(BlogRequestModel model)
        {
            if (model.ID <= 0)
            {
                return new HttpNotFoundResult();
            }

            var result = new BlogViewModel();
            model.CopyTo(result);

            var item = PopulatePost(result);
            if (item == null)
            {
                throw new HttpException(404, "No entry found!");
            }

            result.Articles.Add(item);

            return View("Post", result);
        }

        [HttpPost]
        [ValidateInput(false)]
        public ActionResult Post(CommentRequestModel model)
        {
            try
            {
                if (IsCommentsAllowed && !string.IsNullOrWhiteSpace(model.Comment))
                {
                    var db = new ORM.DB();
                    var post = db.Posts.FirstOrDefault(p => p.ID == model.ID);
                    if (post != null && post.IsCommentable)
                    {
                        // Searching for duplicate
                        var duplicate = db.Comments.FirstOrDefault(c => c.Post != null && c.Post.ID == post.ID && c.Member.ID == Member.Current.ID);
                        if (duplicate == null || duplicate.Content != model.Comment.Trim())
                        {
                            var member = db.Members.First(m => m.ID == Member.Current.ID);
                            var comment = new ORM.Comment()
                                              {
                                                  Post = post,
                                                  Content = model.Comment.Trim(),
                                                  CreatedDate = DateTime.Now,
                                                  ModifiedDate = DateTime.Now,
                                                  Member = member,
                                                  IsPublished = !Configuration.Configuration.Instance.Comments.PublishApprovalRequired,
                                                  IsDeleted = false
                                              };

                            db.AddToComments(comment);
                            post.Comments.Add(comment);

                            db.SaveChanges();
                        }
                    }
                }
            }
            catch (Exception) { }

            return Post((BlogRequestModel)model);
        }

        public ActionResult Page(BlogRequestModel model)
        {
            if (model.ID <= 0)
            {
                return new HttpNotFoundResult();
            }

            var result = new BlogViewModel();
            model.CopyTo(result);

            var item = PopulatePage(result);
            if (item == null)
            {
                throw new HttpException(404, "No entry found!");
            }

            result.Articles.Add(item);

            return View("Page", result);
        }

        [HttpPost]
        [ValidateInput(false)]
        public ActionResult Page(CommentRequestModel model)
        {
            try
            {
                if (IsCommentsAllowed && !string.IsNullOrWhiteSpace(model.Comment))
                {
                    var db = new ORM.DB();
                    var page = db.Pages.FirstOrDefault(p => p.ID == model.ID);
                    if (page != null && page.IsCommentable)
                    {
                        // Searching for duplicate
                        var duplicate = db.Comments.FirstOrDefault(c => c.Page != null && c.Page.ID == page.ID && c.Member.ID == Member.Current.ID);
                        if (duplicate == null || duplicate.Content != model.Comment.Trim())
                        {
                            var member = db.Members.First(m => m.ID == Member.Current.ID);
                            var comment = new ORM.Comment()
                            {
                                Page = page,
                                Content = model.Comment.Trim(),
                                CreatedDate = DateTime.Now,
                                ModifiedDate = DateTime.Now,
                                Member = member,
                                IsPublished = !Configuration.Configuration.Instance.Comments.PublishApprovalRequired,
                                IsDeleted = false
                            };

                            db.AddToComments(comment);
                            page.Comments.Add(comment);

                            db.SaveChanges();
                        }
                    }
                }
            }
            catch (Exception) { }

            return Post((BlogRequestModel)model);
        }

        public ActionResult Tag(BlogRequestModel model)
        {
            if (model.ID < 0 || model.Navigation < 0)
            {
                return new HttpNotFoundResult();
            }

            var result = new TagViewModel();
            model.CopyTo(result);

            var articles = PopulateTags(result);
            result.Articles.AddRange(articles.Articles);
            result.Pagination = articles.Pagination;

            if (result.Articles.Count == 0)
            {
                throw new HttpException(404, "No entries found!");
            }

            return View("Tag", result);
        }

        [ValidateInput(false)]
        public ActionResult Search(SearchRequestModel model)
        {
            if (model.Navigation < 0)
            {
                return new HttpNotFoundResult();
            }

            var result = new SearchViewModel();
            model.CopyTo(result);

            result.Articles.AddRange(PopulateSearch(result));

            return View("Search", result);
        }

        public ActionResult RSS(BlogRequestModel model)
        {
            var result = new BlogViewModel();
            model.CopyTo(result);

            var articles = PopulateSection(result, true);
            result.Articles.AddRange(articles.Articles);
            result.Pagination = articles.Pagination;

            foreach (var article in result.Articles)
            {
                string content = article.Content.Replace("<p>", string.Empty).Replace("</p>", string.Empty).Replace("<br>", string.Empty)
                    .Replace("<br />", string.Empty).Replace("</a>", string.Empty).Replace("<br/>", string.Empty);

                while (content.Contains("<a"))
                {
                    int begin = content.IndexOf("<a");
                    int end = content.IndexOf(">", content.IndexOf("<a")) + 1;
                    content = content.Substring(0, begin) + content.Substring(end);
                }

                if (content.Length > 300)
                {
                    content = content.Substring(0, 300) + "...";
                }

                article.Content = content;
            }

            return View("RSS", result);
        }

        #region Pagination
        public ActionResult Pagination(PaginationRequestModel model)
        {
            return new MenuController().Pagination(model);
        }
        #endregion

        #region Content
        private ArticleCollectionWrapper PopulateSection(BlogViewModel model, bool skipPinned = false)
        {
            ArticleCollectionWrapper result = new ArticleCollectionWrapper();
            int pinnedCount = 0;
            int notPinnedCount = 0;

            if (model.Categories.Any())
            {
                // Add pages
                int pinnedPagesCount = 0;
                if (!skipPinned)
                {
                    var pinnedPages = model.Pages.Where(p => p.IsSectionPinned && p.IsPublished).OrderBy(p => p.Order);

                    pinnedPagesCount = pinnedPages.Count();

                    result.Articles.AddRange(pinnedPages.Select(a => a.ToArticle()));
                }

                // Collect section pinned posts
                if (!skipPinned)
                {
                    var pinned = model.Categories.SelectMany(c => c.PostCategories)
                        .Select(pc => pc.Post).Where(p => p.IsSectionPinned && p.IsPublished).Distinct().OrderByDescending(p => p.CreatedDate).ThenByDescending(p => p.ID);

                    pinnedCount = pinned.Count();

                    result.Articles.AddRange(pinned.Select(a => a.ToArticle()));
                }

                // Collect not pinned posts
                int skipCount = MaxPageSize * model.Navigation;
                int takeCount = MaxPageSize - (result.Articles.Count - pinnedPagesCount);

                var posts = model.Categories.SelectMany(c => c.PostCategories).Select(pc => pc.Post)
                    .Where(p => !p.IsCategoryPinned && p.IsPublished).Distinct();

                notPinnedCount = posts.Count();

                result.Articles.AddRange(posts.OrderByDescending(p => p.CreatedDate).ThenByDescending(p => p.ID).Skip(skipCount).Take(takeCount).Select(a => a.ToArticle()));
            }
            else if (model.Pages.Any())
            {
                // Collect section pinned posts
                if (!skipPinned)
                {
                    var pinned = model.Pages.Where(p => p.IsSectionPinned && p.IsPublished).Distinct().OrderBy(p => p.Order);

                    pinnedCount = pinned.Count();

                    result.Articles.AddRange(pinned.Select(a => a.ToArticle()));
                }

                // Collect not pinned posts
                int skipCount = MaxPageSize * model.Navigation;
                int takeCount = MaxPageSize - result.Articles.Count;

                var pages = model.Pages.Where(p => !p.IsSectionPinned && p.IsPublished).Distinct();

                notPinnedCount = pages.Count();

                result.Articles.AddRange(pages.OrderByDescending(p => p.CreatedDate).ThenByDescending(p => p.ID).Skip(skipCount).Take(takeCount).Select(a => a.ToArticle()));
            }

            // Calculate pagination
            result.Pagination.CurrentPage = model.ID + 1;
            result.Pagination.TotalPages = (MaxPageSize - pinnedCount) <= 0 ? int.MaxValue : (int)Math.Ceiling((double)notPinnedCount / (MaxPageSize - pinnedCount));

            return result;
        } 

        private ArticleCollectionWrapper PopulateCategory(CategoryViewModel model)
        {
            ArticleCollectionWrapper result = new ArticleCollectionWrapper();
            int pinnedCount = 0;
            int notPinnedCount = 0;

            // Collect section pinned posts
            var pinned = model.Category.PostCategories
                .Select(pc => pc.Post).Where(p => p.IsCategoryPinned && p.IsPublished).Distinct();

            pinnedCount = pinned.Count();

            result.Articles.AddRange(pinned.OrderByDescending(p => p.CreatedDate).Select(a => a.ToArticle()));

            // Collect not pinned posts
            int skipCount = MaxPageSize * model.Navigation;
            int takeCount = MaxPageSize - result.Articles.Count;

            var posts = model.Category.PostCategories.Select(pc => pc.Post).Where(p => !p.IsCategoryPinned && p.IsPublished).Distinct();

            notPinnedCount = posts.Count();

            result.Articles.AddRange(posts.OrderByDescending(p => p.CreatedDate).Skip(skipCount).Take(takeCount).Select(a => a.ToArticle()));

            // Calculate pagination
            result.Pagination.CurrentPage = model.Navigation + 1;
            result.Pagination.TotalPages = (MaxPageSize - pinnedCount) <= 0 ? int.MaxValue : (int)Math.Ceiling((double)notPinnedCount / (MaxPageSize - pinnedCount));

            return result;
        }

        private ArticleCollectionWrapper PopulateArchive(ArchiveViewModel model)
        {
            ArticleCollectionWrapper result = new ArticleCollectionWrapper();
            int notPinnedCount = 0;

            model.Year = model.ID/100;
            model.Month = model.ID%100;

            int skipCount = MaxPageSize*model.Navigation;
            int takeCount = MaxPageSize;

            var posts = model.Categories.SelectMany(c => c.PostCategories).Select(pc => pc.Post)
                .Where(p => p.IsPublished && (p.CreatedDate.Year == model.Year && p.CreatedDate.Month == model.Month)).Distinct();

            notPinnedCount = posts.Count();

            result.Articles.AddRange(posts.OrderByDescending(p => p.CreatedDate).Skip(skipCount).Take(takeCount).Select(a => a.ToArticle()));

            // Calculate pagination
            result.Pagination.CurrentPage = model.Navigation + 1;
            result.Pagination.TotalPages = (int)Math.Ceiling((double)notPinnedCount/MaxPageSize);

            return result;
        }

        private Article PopulatePost(BlogViewModel model)
        {
            var db = new ORM.DB();
            var item = db.Posts.FirstOrDefault(p => p.ID == model.ID);

            if (item == null || !item.IsPublished)
            {
                return null;
            }

            item.ViewCount++;
            db.SaveChanges();

            var result = item.ToArticle();

            result.Tags = item.PostTags.Select(pt => pt.Tag);

            var comments = item.Comments.Where(c => (c.IsPublished && !c.IsDeleted) || (Member.Current.ID != 0 && (!c.IsDeleted && c.Member.ID == Member.Current.ID)));

            result.Comments = comments.OrderBy(c => c.ID);

            return result;
        }

        private Article PopulatePage(BlogViewModel model)
        {
            var db = new ORM.DB();
            var item = db.Pages.FirstOrDefault(p => p.ID == model.ID);

            if (item == null || !item.IsPublished)
            {
                return null;
            }

            item.ViewCount++;
            db.SaveChanges();

            var result = item.ToArticle();

            result.Tags = item.PageTags.Select(pt => pt.Tag);

            var comments = item.Comments.Where(c => (c.IsPublished && !c.IsDeleted) || (Member.Current.ID != 0 && (!c.IsDeleted && c.Member.ID == Member.Current.ID)));

            result.Comments = comments.OrderBy(c => c.CreatedDate);

            return result;
        }

        private ArticleCollectionWrapper PopulateTags(BlogViewModel model)
        {
            ArticleCollectionWrapper result = new ArticleCollectionWrapper();

            int skipCount = MaxPageSize*model.Navigation;
            int takeCount = MaxPageSize;

            var tag = model.Tags.FirstOrDefault(t => t.ID == model.ID);

            if (tag != null)
            {
                int notPinnedCount = 0;
                var posts = tag.PostTags.Select(pt => pt.Post).Where(p => p.IsPublished).OrderByDescending(p => p.CreatedDate);
                notPinnedCount = posts.Count();

                result.Articles.AddRange(posts.Skip(skipCount).Take(takeCount).Select(a => a.ToArticle()));

                // Calculate pagination
                result.Pagination.CurrentPage = model.Navigation + 1;
                result.Pagination.TotalPages = (int)Math.Ceiling((double)notPinnedCount / MaxPageSize);
            }

            return result;
        }


        private List<Article> PopulateSearch(SearchViewModel model)
        {
            List<Article> result = new List<Article>();

            int skipCount = MaxPageSize * model.Navigation;
            int takeCount = MaxPageSize;

            var searchResult = model.Search(skipCount, takeCount);

            result.AddRange(searchResult);

            return result;
        }

        #endregion
    }
}
